Here we will map an example dataset from Roy et al (Cell Rep 2019)
comprising 12,245 CD34+ cells from fetal and adult bone marrow. Note
that any human hematopoietic cells, regardless of tissue source or
disease status, can be mapped to this reference. This tutorial will take
approximately 10 minutes to run.
Setup
library(Seurat)
library(tidyverse)
library(symphony)
library(ggpubr)
library(patchwork)
Install package from github
## install dependencies that are not on CRAN
if(!require(BiocManager, quietly = TRUE)) install.packages("BiocManager")
BiocManager::install(c("AUCell", "doMC"))
if(!require(devtools, quietly = TRUE)) install.packages("devtools")
devtools::install_github("jaredhuling/jcolors")
## install BoneMarrowMap package
devtools::install_github('andygxzeng/BoneMarrowMap', force = TRUE)
Downloading GitHub repo andygxzeng/BoneMarrowMap@HEAD
HEMATOPOIESIS - Download reference object and UMAP model
Set projection folder, Download reference object and UMAP model
# Set directory to store projection reference files
projection_path = './'
# Download Bone Marrow Reference - 344 Mb
curl::curl_download('https://bonemarrowmap.s3.us-east-2.amazonaws.com/BoneMarrow_RefMap_SymphonyRef.rds',
destfile = paste0(projection_path, 'BoneMarrow_RefMap_SymphonyRef.rds'))
# Download uwot model file - 221 Mb
curl::curl_download('https://bonemarrowmap.s3.us-east-2.amazonaws.com/BoneMarrow_RefMap_uwot_model.uwot',
destfile = paste0(projection_path, 'BoneMarrow_RefMap_uwot_model.uwot'))
# Load Symphony reference
ref <- readRDS(paste0(projection_path, 'BoneMarrow_RefMap_SymphonyRef.rds'))
# Set uwot path for UMAP projection
ref$save_uwot_path <- paste0(projection_path, 'BoneMarrow_RefMap_uwot_model.uwot')
Visualize Bone Marrow Reference
If we want to visualize celltype labels or metadata from the BM
Reference, we can create a Seurat Object from the symphony reference
This will be memory efficient as it will not include gene expression
counts, only the UMAP coordinates and the metadata including cell labels
and sorting information
BM_ref_obj <- create_ReferenceObject(BM_ref)
DimPlot(BM_ref_obj, reduction = 'umap', group.by = 'CellType_Annotation_formatted', raster=FALSE, label=TRUE, label.size = 4)

We can visualize other annotations too, including cell cycle phase
and lineage pseudotime estimates.
p1 <- DimPlot(BM_ref_obj, reduction = 'umap', group.by = 'CyclePhase', raster=FALSE)
p2 <- FeaturePlot(BM_ref_obj, reduction = 'umap', features = 'Pseudotime', raster=FALSE)
p1 + p2

Load Leukemia scRNA-seq data.
Let’s use these maps to project scRNA-seq data from two Ph+ B-ALL
patients with distinct cell compositions (SJPHALL006_D,
SJPHALL007_D)
Each patient sample was downsampled to 2500 cells to decrease runtime
for this example.
query <- readRDS('ExampleQuery_BALL_scRNAseq.rds')
query
An object of class Seurat
36601 features across 13265 samples within 1 assay
Active assay: RNA (36601 features, 0 variable features)
Map the Query Data
Provide raw counts, metadata, and donor key. This should take <1
min Calculate mapping error and perform QC to remove low quality cells
with high mapping error
# batch variable to correct in the query data, set as NULL if no batches in query
batchvar <- 'Sample'
# Map query dataset using Symphony (Kang et al 2021)
query <- map_Query(
exp_query = query@assays$RNA@counts,
metadata_query = query@meta.data,
ref_obj = BM_ref,
vars = batchvar
)
Normalizing
Scaling and synchronizing query gene expression
Found 2360 reference variable genes in query dataset
Project query cells using reference gene loadings
Clustering query cells to reference centroids
Correcting query batch effects
UMAP
All done!
Warning: Adding features not currently present in the objectWarning: Invalid name supplied, making object name syntactically valid. New object name is Seurat..ProjectDim.RNA.harmony; see ?make.names for more details on syntax validity
In leukemia samples, the distribution of mapping error scores can
vary broadly from sample to sample. In this context, we will want to
threshold outliers with high mapping error on a per-sample basis.
Typically, a threshold of 2, 2.5, or 3 MADs works well.
In some cases where sequencing depth is very low (e.g. older datasets
from first-generation scRNA-seq protocols), a more stringent threshold
of even 1.5 may be warranted to eliminate cells with low mapping
quality
# Run QC based on mapping error score, flag cells with mapping error >= 2.5 MADs above median
query <- query %>% calculate_MappingError(., reference = BM_ref, MAD_threshold = 2.5,
threshold_by_donor = TRUE, donor_key = batchvar) # threshold mapping error on a per-sample basis.
# Plot distribution by patient to ensure you are catching the tail
query@meta.data %>%
ggplot(aes(x = mapping_error_score, fill = mapping_error_QC)) +
geom_histogram(bins = 200) + facet_wrap(.~get(batchvar))

# Get QC Plots
QC_plots <- plot_MappingErrorQC(query)
# Plot together - If this is too crowded, can also just call "QC_plots" aloneto display one by one
patchwork::wrap_plots(QC_plots, ncol = 4, widths = c(0.8, 0.3, 0.8, 0.3))

This important step identifies a subset of cells with high mapping
error from the query dataset that are either:
- not present within the reference, or
- have poor QC metrics (low RNA counts and low transcriptional
diversity)
Sometimes, low quality cells may erroneously map to the
orthochromatic erythroblast region as this cell type has very low
transcriptional diversity. These low quality query cells do not have
hemoglobin expression and are in fact mis-mapped; they will be flagged
by the QC filter and excluded from cell type assignments.
Please adjust the MAD_threshold (typically between 1 and 3)
based on the distribution of your dataset to identify the outliers with
low quality and high mapping error scores. This will improve your
classifications and any downstream composition analysis
# # Optional step - remove outliers with high mapping error
# query <- subset(query, mapping_error_QC == 'Pass')
Optionally, outlier cells with high mapping error can also be removed
at this stage. For ease of integrating these mapped annotations with the
rest of your analysis, we can choose to skip this step. If so, Final
CellType and Pseudotime predictions will be assigned as NA for cells
failing the mapping error QC threshold.
Cell Type Assignments
We will next use a KNN classifier to assign cell identity based on
the 30 K-Nearest Neighbours from the reference map. This label transfer
step will take longer, potentially around 10 minutes for ~10,000
cells
# Predict Hematopoietic Cell Types by KNN classification
query <- predict_CellTypes(
query_obj = query,
ref_obj = BM_ref,
initial_label = 'initial_CellType_BoneMarrowMap', # celltype assignments before filtering on mapping QC
final_label = 'predicted_CellType_BoneMarrowMap' # celltype assignments with map QC failing cells assigned as NA
)
DimPlot(subset(query, mapping_error_QC == 'Pass'), reduction = 'umap', group.by = c('predicted_CellType_BoneMarrowMap'),
raster=FALSE, label=TRUE, label.size = 4)

Pseudotime Annotations
We can also annotate each query cell based on their position along
hematopoietic pseudotime. Query cells will be assigned a pseudotime
score based on the 30 K-Nearest Neighbours from the reference map. Since
our Pseudotime KNN assignments are performed in UMAP space (more
accurate than KNN on harmony components), this step is very fast (<
10s)
library(RColorBrewer)
# Predict Pseudotime values by KNN
query <- predict_Pseudotime(
query_obj = query,
ref_obj = BM_ref,
initial_label = 'initial_Pseudotime', # pseudotime assignments before filtering on mapping QC
final_label = 'predicted_Pseudotime' # pseudotime assignments with map QC failing cells assigned as NA
)
# Visualize Hematopoietic Pseudotime in query data
FeaturePlot(subset(query, mapping_error_QC == 'Pass'), features = c('predicted_Pseudotime'), split.by = 'Sample') &
scale_color_gradientn(colors = rev(brewer.pal(11, 'RdBu')))
Scale for colour is already present.
Adding another scale for colour, which will replace the existing scale.Scale for colour is already present.
Adding another scale for colour, which will replace the existing scale.

Visualize Projection Density
Now let’s visualize the density distribution of query cells across
the hematopoietic hierarchy
# Set batch/condition to be visualized individually
batch_key <- 'Sample'
# returns a list of plots for each donor from a pre-specified batch variable
projection_plots <- plot_Projection_byDonor(
query_obj = query,
batch_key = batch_key,
ref_obj = BM_ref,
Hierarchy_only = FALSE, # Whether to exclude T/NK/Plasma/Stromal cells
downsample_reference = TRUE,
downsample_frac = 0.25, # down-sample reference cells to 25%; reduces figure file size
query_point_size = 0.2, # adjust size of query cells based on # of cells
saveplot = TRUE,
save_folder = 'projectionFigures/'
)
# show plots together with patchwork. Can also just call "projection_plots" object to display one-by-one
patchwork::wrap_plots(projection_plots, ncol = 2)

We can also set Hierarchy_only = TRUE to remove T/NK/Plasma/Stromal
cells and focus solely on the hematopoietic hierarchy.
# Set batch/condition to be visualized individually
batch_key <- 'Sample'
# returns a list of plots for each donor from a pre-specified batch variable
projection_plots <- plot_Projection_byDonor(
query_obj = query,
batch_key = batch_key,
ref_obj = BM_ref,
Hierarchy_only = TRUE, # Whether to exclude T/NK/Plasma/Stromal cells
downsample_reference = TRUE,
downsample_frac = 0.25, # down-sample reference cells to 25%; reduces figure file size
query_point_size = 0.2, # adjust size of query cells based on # of cells
saveplot = TRUE,
save_folder = 'projectionFigures/'
)
# show plots together with patchwork. Can also just call "projection_plots" object to display one-by-one
patchwork::wrap_plots(projection_plots, ncol = 2)

Let’s save the results of the mapping onto the complete hematopoietic
hierarchy.
saveRDS(query, 'QueryData_Mapped_CompleteHematopoiesis.rds')
B CELL DEVELOPMENT - Download reference object and UMAP model
Set projection folder, Download reference object and UMAP model
# Set directory to store projection reference files
projection_path = './'
# # Download Bone Marrow Reference - 187 Mb
# curl::curl_download('https://bdevelopmentmap.s3.us-east-2.amazonaws.com/BDevelopment_RefMap_SymphonyRef.rds',
# destfile = paste0(projection_path, 'BDevelopment_RefMap_SymphonyRef.rds'))
# # Download uwot model file - 99 Mb
# curl::curl_download('https://bdevelopmentmap.s3.us-east-2.amazonaws.com/BDevelopment_RefMap_uwot_model.uwot',
# destfile = paste0(projection_path, 'BDevelopment_RefMap_uwot_model.uwot'))
# Load Symphony reference
bdev_ref <- readRDS(paste0(projection_path, 'BDevelopment_RefMap_SymphonyRef.rds'))
bdev_ref$umap$embedding[,1] = -bdev_ref$umap$embedding[,1]
# Set uwot path for UMAP projection
bdev_ref$save_uwot_path <- paste0(projection_path, 'BDevelopment_RefMap_uwot_model.uwot')
Visualize Bone Marrow Reference
If we want to visualize celltype labels or metadata from the BM
Reference, we can create a Seurat Object from the symphony reference
This will be memory efficient as it will not include gene expression
counts, only the UMAP coordinates and the metadata including cell labels
and sorting information
bdev_ref_obj <- create_ReferenceObject(bdev_ref)
DimPlot(bdev_ref_obj, reduction = 'umap', group.by = 'BDevelopment_CellType', raster=FALSE, label=TRUE, label.size = 4)

Mapping query data onto B cell development
query <- readRDS('QueryData_Mapped_CompleteHematopoiesis.rds')
query
An object of class Seurat
36601 features across 13265 samples within 1 assay
Active assay: RNA (36601 features, 0 variable features)
3 dimensional reductions calculated: pca, harmony, umap
Filter for celltypes along B cell development
B_development_celltypes <- c('HSC', 'HSC/MPP', 'MPP-MyLy', 'MPP-LMPP', 'LMPP', 'Early GMP', 'MLP', 'MLP-II', 'Pre-pDC', 'Pre-pDC Cycling', 'pDC',
'CLP', 'EarlyProB', 'Pre-ProB', 'Pro-B VDJ', 'Pro-B Cycling', 'Large Pre-B', 'Small Pre-B', 'Immature B', 'Mature B')
query <- subset(query, predicted_CellType_BoneMarrowMap %in% B_development_celltypes)
query
An object of class Seurat
36601 features across 11572 samples within 1 assay
Active assay: RNA (36601 features, 0 variable features)
3 dimensional reductions calculated: pca, harmony, umap
Map the Query Data
Provide raw counts, metadata, and donor key. This should take <1
min Calculate mapping error and perform QC to remove low quality cells
with high mapping error
# Map query dataset using Symphony (Kang et al 2021)
query <- map_Query(
exp_query = query@assays$RNA@counts,
metadata_query = query@meta.data,
ref_obj = bdev_ref,
vars = batchvar
)
Normalizing
Scaling and synchronizing query gene expression
Found 950 reference variable genes in query dataset
Project query cells using reference gene loadings
Clustering query cells to reference centroids
Correcting query batch effects
UMAP
All done!
Warning: Invalid name supplied, making object name syntactically valid. New object name is Seurat..ProjectDim.RNA.harmony; see ?make.names for more details on syntax validity
# Flip UMAP1 to go from left to right
query[['umap']]@cell.embeddings[,1] = -query[['umap']]@cell.embeddings[,1]
query[['umap']]
bdev_ref$meta_data
Cell Type Assignments
We will next use a KNN classifier to assign cell identity based on
the 30 K-Nearest Neighbours from the reference map. This label transfer
step will take longer, potentially around 10 minutes for ~10,000
cells
# Predict Hematopoietic Cell Types by KNN classification
query <- predict_CellTypes(
query_obj = query,
ref_obj = bdev_ref,
ref_label = 'BDevelopment_CellType', ## for a more detailed annotation, use BDevelopment_CellType_Comprehensive
initial_label = 'initial_CellType_BDevelopment', # celltype assignments before filtering on mapping QC
final_label = 'predicted_CellType_BDevelopment' # celltype assignments with map QC failing cells assigned as NA
)
DimPlot(subset(query, mapping_error_QC == 'Pass'), reduction = 'umap', group.by = c('predicted_CellType_BDevelopment'),
raster=FALSE, label=TRUE, label.size = 4)

Visualize Projection Density
Now let’s visualize the density distribution of query cells across
the hematopoietic hierarchy

saveRDS(query, 'QueryData_Mapped_BcellDevelopment.rds')
LS0tCnRpdGxlOiAiUiBOb3RlYm9vayIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKSGVyZSB3ZSB3aWxsIG1hcCBhbiBleGFtcGxlIGRhdGFzZXQgZnJvbSBSb3kgZXQgYWwgKENlbGwgUmVwIDIwMTkpIGNvbXByaXNpbmcgMTIsMjQ1IENEMzQrIGNlbGxzIGZyb20gZmV0YWwgYW5kIGFkdWx0IGJvbmUgbWFycm93LgpOb3RlIHRoYXQgYW55IGh1bWFuIGhlbWF0b3BvaWV0aWMgY2VsbHMsIHJlZ2FyZGxlc3Mgb2YgdGlzc3VlIHNvdXJjZSBvciBkaXNlYXNlIHN0YXR1cywgY2FuIGJlIG1hcHBlZCB0byB0aGlzIHJlZmVyZW5jZS4KVGhpcyB0dXRvcmlhbCB3aWxsIHRha2UgYXBwcm94aW1hdGVseSAxMCBtaW51dGVzIHRvIHJ1bi4KCiMjIyBTZXR1cAoKYGBge3J9CmxpYnJhcnkoU2V1cmF0KQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShzeW1waG9ueSkKbGlicmFyeShnZ3B1YnIpCmxpYnJhcnkocGF0Y2h3b3JrKQpgYGAKCkluc3RhbGwgcGFja2FnZSBmcm9tIGdpdGh1YgoKYGBge3J9CiMjIGluc3RhbGwgZGVwZW5kZW5jaWVzIHRoYXQgYXJlIG5vdCBvbiBDUkFOCmlmKCFyZXF1aXJlKEJpb2NNYW5hZ2VyLCBxdWlldGx5ID0gVFJVRSkpIGluc3RhbGwucGFja2FnZXMoIkJpb2NNYW5hZ2VyIikKQmlvY01hbmFnZXI6Omluc3RhbGwoYygiQVVDZWxsIiwgImRvTUMiKSkKaWYoIXJlcXVpcmUoZGV2dG9vbHMsIHF1aWV0bHkgPSBUUlVFKSkgaW5zdGFsbC5wYWNrYWdlcygiZGV2dG9vbHMiKQpkZXZ0b29sczo6aW5zdGFsbF9naXRodWIoImphcmVkaHVsaW5nL2pjb2xvcnMiKQpgYGAKIApgYGB7cn0KIyMgaW5zdGFsbCBCb25lTWFycm93TWFwIHBhY2thZ2UKZGV2dG9vbHM6Omluc3RhbGxfZ2l0aHViKCdhbmR5Z3h6ZW5nL0JvbmVNYXJyb3dNYXAnLCBmb3JjZSA9IFRSVUUpCmxpYnJhcnkoQm9uZU1hcnJvd01hcCkKYGBgCgogCiMjIyMgSEVNQVRPUE9JRVNJUyAtIERvd25sb2FkIHJlZmVyZW5jZSBvYmplY3QgYW5kIFVNQVAgbW9kZWwKClNldCBwcm9qZWN0aW9uIGZvbGRlciwgRG93bmxvYWQgcmVmZXJlbmNlIG9iamVjdCBhbmQgVU1BUCBtb2RlbAoKYGBge3J9CiMgU2V0IGRpcmVjdG9yeSB0byBzdG9yZSBwcm9qZWN0aW9uIHJlZmVyZW5jZSBmaWxlcwpwcm9qZWN0aW9uX3BhdGggPSAnLi8nCgojIERvd25sb2FkIEJvbmUgTWFycm93IFJlZmVyZW5jZSAtIDM0NCBNYgpjdXJsOjpjdXJsX2Rvd25sb2FkKCdodHRwczovL2JvbmVtYXJyb3dtYXAuczMudXMtZWFzdC0yLmFtYXpvbmF3cy5jb20vQm9uZU1hcnJvd19SZWZNYXBfU3ltcGhvbnlSZWYucmRzJywgCiAgICAgICAgICAgICAgICAgICAgZGVzdGZpbGUgPSBwYXN0ZTAocHJvamVjdGlvbl9wYXRoLCAnQm9uZU1hcnJvd19SZWZNYXBfU3ltcGhvbnlSZWYucmRzJykpCiMgRG93bmxvYWQgdXdvdCBtb2RlbCBmaWxlIC0gMjIxIE1iCmN1cmw6OmN1cmxfZG93bmxvYWQoJ2h0dHBzOi8vYm9uZW1hcnJvd21hcC5zMy51cy1lYXN0LTIuYW1hem9uYXdzLmNvbS9Cb25lTWFycm93X1JlZk1hcF91d290X21vZGVsLnV3b3QnLCAKICAgICAgICAgICAgICAgICAgICBkZXN0ZmlsZSA9IHBhc3RlMChwcm9qZWN0aW9uX3BhdGgsICdCb25lTWFycm93X1JlZk1hcF91d290X21vZGVsLnV3b3QnKSkKCiMgTG9hZCBTeW1waG9ueSByZWZlcmVuY2UKQk1fcmVmIDwtIHJlYWRSRFMocGFzdGUwKHByb2plY3Rpb25fcGF0aCwgJ0JvbmVNYXJyb3dfUmVmTWFwX1N5bXBob255UmVmLnJkcycpKQojIFNldCB1d290IHBhdGggZm9yIFVNQVAgcHJvamVjdGlvbgpCTV9yZWYkc2F2ZV91d290X3BhdGggPC0gcGFzdGUwKHByb2plY3Rpb25fcGF0aCwgJ0JvbmVNYXJyb3dfUmVmTWFwX3V3b3RfbW9kZWwudXdvdCcpCmBgYAoKCiMjIyMgVmlzdWFsaXplIEJvbmUgTWFycm93IFJlZmVyZW5jZQoKSWYgd2Ugd2FudCB0byB2aXN1YWxpemUgY2VsbHR5cGUgbGFiZWxzIG9yIG1ldGFkYXRhIGZyb20gdGhlIEJNIFJlZmVyZW5jZSwgd2UgY2FuIGNyZWF0ZSBhIFNldXJhdCBPYmplY3QgZnJvbSB0aGUgc3ltcGhvbnkgcmVmZXJlbmNlIApUaGlzIHdpbGwgYmUgbWVtb3J5IGVmZmljaWVudCBhcyBpdCB3aWxsIG5vdCBpbmNsdWRlIGdlbmUgZXhwcmVzc2lvbiBjb3VudHMsIG9ubHkgdGhlIFVNQVAgY29vcmRpbmF0ZXMgYW5kIHRoZSBtZXRhZGF0YSBpbmNsdWRpbmcgY2VsbCBsYWJlbHMgYW5kIHNvcnRpbmcgaW5mb3JtYXRpb24KCmBgYHtyLCBmaWcuaGVpZ2h0PTUsIGZpZy53aWR0aD0xMX0KQk1fcmVmX29iaiA8LSBjcmVhdGVfUmVmZXJlbmNlT2JqZWN0KEJNX3JlZikKCkRpbVBsb3QoQk1fcmVmX29iaiwgcmVkdWN0aW9uID0gJ3VtYXAnLCBncm91cC5ieSA9ICdDZWxsVHlwZV9Bbm5vdGF0aW9uX2Zvcm1hdHRlZCcsIHJhc3Rlcj1GQUxTRSwgbGFiZWw9VFJVRSwgbGFiZWwuc2l6ZSA9IDQpCmBgYAoKV2UgY2FuIHZpc3VhbGl6ZSBvdGhlciBhbm5vdGF0aW9ucyB0b28sIGluY2x1ZGluZyBjZWxsIGN5Y2xlIHBoYXNlIGFuZCBsaW5lYWdlIHBzZXVkb3RpbWUgZXN0aW1hdGVzLgoKYGBge3IsIGZpZy5oZWlnaHQ9My41LCBmaWcud2lkdGg9MTF9CnAxIDwtIERpbVBsb3QoQk1fcmVmX29iaiwgcmVkdWN0aW9uID0gJ3VtYXAnLCBncm91cC5ieSA9ICdDeWNsZVBoYXNlJywgcmFzdGVyPUZBTFNFKQpwMiA8LSBGZWF0dXJlUGxvdChCTV9yZWZfb2JqLCByZWR1Y3Rpb24gPSAndW1hcCcsIGZlYXR1cmVzID0gJ1BzZXVkb3RpbWUnLCByYXN0ZXI9RkFMU0UpIAoKcDEgKyBwMgpgYGAKCgojIyMjIExvYWQgTGV1a2VtaWEgc2NSTkEtc2VxIGRhdGEuCgpMZXQncyB1c2UgdGhlc2UgbWFwcyB0byBwcm9qZWN0IHNjUk5BLXNlcSBkYXRhIGZyb20gdHdvIFBoKyBCLUFMTCBwYXRpZW50cyB3aXRoIGRpc3RpbmN0IGNlbGwgY29tcG9zaXRpb25zIChTSlBIQUxMMDA2X0QsIFNKUEhBTEwwMDdfRCkKCkVhY2ggcGF0aWVudCBzYW1wbGUgd2FzIGRvd25zYW1wbGVkIHRvIDI1MDAgY2VsbHMgdG8gZGVjcmVhc2UgcnVudGltZSBmb3IgdGhpcyBleGFtcGxlLiAKCmBgYHtyfQpjdXJsOjpjdXJsX2Rvd25sb2FkKCdodHRwczovL2JkZXZlbG9wbWVudG1hcC5zMy51cy1lYXN0LTIuYW1hem9uYXdzLmNvbS9FeGFtcGxlUXVlcnlfQkFMTF9zY1JOQXNlcS5yZHMnLCAKICAgICAgICAgICAgICAgICAgICBkZXN0ZmlsZSA9IHBhc3RlMChwcm9qZWN0aW9uX3BhdGgsICdFeGFtcGxlUXVlcnlfQkFMTF9zY1JOQXNlcS5yZHMnKSkKCnF1ZXJ5IDwtIHJlYWRSRFMoJ0V4YW1wbGVRdWVyeV9CQUxMX3NjUk5Bc2VxLnJkcycpCnF1ZXJ5CmBgYAoKCiMjIyBNYXAgdGhlIFF1ZXJ5IERhdGEKUHJvdmlkZSByYXcgY291bnRzLCBtZXRhZGF0YSwgYW5kIGRvbm9yIGtleS4gVGhpcyBzaG91bGQgdGFrZSA8MSBtaW4KQ2FsY3VsYXRlIG1hcHBpbmcgZXJyb3IgYW5kIHBlcmZvcm0gUUMgdG8gcmVtb3ZlIGxvdyBxdWFsaXR5IGNlbGxzIHdpdGggaGlnaCBtYXBwaW5nIGVycm9yCgpgYGB7cn0KIyBiYXRjaCB2YXJpYWJsZSB0byBjb3JyZWN0IGluIHRoZSBxdWVyeSBkYXRhLCBzZXQgYXMgTlVMTCBpZiBubyBiYXRjaGVzIGluIHF1ZXJ5CmJhdGNodmFyIDwtICdTYW1wbGUnCgojIE1hcCBxdWVyeSBkYXRhc2V0IHVzaW5nIFN5bXBob255IChLYW5nIGV0IGFsIDIwMjEpCnF1ZXJ5IDwtIG1hcF9RdWVyeSgKICAgIGV4cF9xdWVyeSA9IHF1ZXJ5QGFzc2F5cyRSTkFAY291bnRzLCAKICAgIG1ldGFkYXRhX3F1ZXJ5ID0gcXVlcnlAbWV0YS5kYXRhLAogICAgcmVmX29iaiA9IEJNX3JlZiwKICAgIHZhcnMgPSBiYXRjaHZhcgopCmBgYAoKSW4gbGV1a2VtaWEgc2FtcGxlcywgdGhlIGRpc3RyaWJ1dGlvbiBvZiBtYXBwaW5nIGVycm9yIHNjb3JlcyBjYW4gdmFyeSBicm9hZGx5IGZyb20gc2FtcGxlIHRvIHNhbXBsZS4gSW4gdGhpcyBjb250ZXh0LCB3ZSB3aWxsIHdhbnQgdG8gdGhyZXNob2xkIG91dGxpZXJzIHdpdGggaGlnaCBtYXBwaW5nIGVycm9yIG9uIGEgcGVyLXNhbXBsZSBiYXNpcy4gVHlwaWNhbGx5LCBhIHRocmVzaG9sZCBvZiAyLCAyLjUsIG9yIDMgTUFEcyB3b3JrcyB3ZWxsLiAKCkluIHNvbWUgY2FzZXMgd2hlcmUgc2VxdWVuY2luZyBkZXB0aCBpcyB2ZXJ5IGxvdyAoZS5nLiBvbGRlciBkYXRhc2V0cyBmcm9tIGZpcnN0LWdlbmVyYXRpb24gc2NSTkEtc2VxIHByb3RvY29scyksIGEgbW9yZSBzdHJpbmdlbnQgdGhyZXNob2xkIG9mIGV2ZW4gMS41IG1heSBiZSB3YXJyYW50ZWQgdG8gZWxpbWluYXRlIGNlbGxzIHdpdGggbG93IG1hcHBpbmcgcXVhbGl0eSAKCmBgYHtyLCBmaWcuaGVpZ2h0PTMsIGZpZy53aWR0aD0xMH0KIyBSdW4gUUMgYmFzZWQgb24gbWFwcGluZyBlcnJvciBzY29yZSwgZmxhZyBjZWxscyB3aXRoIG1hcHBpbmcgZXJyb3IgPj0gMi41IE1BRHMgYWJvdmUgbWVkaWFuCnF1ZXJ5IDwtIHF1ZXJ5ICU+JSBjYWxjdWxhdGVfTWFwcGluZ0Vycm9yKC4sIHJlZmVyZW5jZSA9IEJNX3JlZiwgTUFEX3RocmVzaG9sZCA9IDIuNSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRocmVzaG9sZF9ieV9kb25vciA9IFRSVUUsIGRvbm9yX2tleSA9IGJhdGNodmFyKSAjIHRocmVzaG9sZCBtYXBwaW5nIGVycm9yIG9uIGEgcGVyLXNhbXBsZSBiYXNpcy4KCiMgUGxvdCBkaXN0cmlidXRpb24gYnkgcGF0aWVudCB0byBlbnN1cmUgeW91IGFyZSBjYXRjaGluZyB0aGUgdGFpbApxdWVyeUBtZXRhLmRhdGEgJT4lIAogIGdncGxvdChhZXMoeCA9IG1hcHBpbmdfZXJyb3Jfc2NvcmUsIGZpbGwgPSBtYXBwaW5nX2Vycm9yX1FDKSkgKyAKICBnZW9tX2hpc3RvZ3JhbShiaW5zID0gMjAwKSArIGZhY2V0X3dyYXAoLn5nZXQoYmF0Y2h2YXIpKQpgYGAKCgpgYGB7ciwgZmlnLmhlaWdodD0zLCBmaWcud2lkdGg9MTB9CiMgR2V0IFFDIFBsb3RzClFDX3Bsb3RzIDwtIHBsb3RfTWFwcGluZ0Vycm9yUUMocXVlcnkpCgojIFBsb3QgdG9nZXRoZXIgLSBJZiB0aGlzIGlzIHRvbyBjcm93ZGVkLCBjYW4gYWxzbyBqdXN0IGNhbGwgIlFDX3Bsb3RzIiBhbG9uZXRvIGRpc3BsYXkgb25lIGJ5IG9uZQpwYXRjaHdvcms6OndyYXBfcGxvdHMoUUNfcGxvdHMsIG5jb2wgPSA0LCB3aWR0aHMgPSBjKDAuOCwgMC4zLCAwLjgsIDAuMykpCmBgYAoKClRoaXMgaW1wb3J0YW50IHN0ZXAgaWRlbnRpZmllcyBhIHN1YnNldCBvZiBjZWxscyB3aXRoIGhpZ2ggbWFwcGluZyBlcnJvciBmcm9tIHRoZSBxdWVyeSBkYXRhc2V0IHRoYXQgYXJlIGVpdGhlcjoKCiogbm90IHByZXNlbnQgd2l0aGluIHRoZSByZWZlcmVuY2UsIG9yCiogaGF2ZSBwb29yIFFDIG1ldHJpY3MgKGxvdyBSTkEgY291bnRzIGFuZCBsb3cgdHJhbnNjcmlwdGlvbmFsIGRpdmVyc2l0eSkKClNvbWV0aW1lcywgbG93IHF1YWxpdHkgY2VsbHMgbWF5IGVycm9uZW91c2x5IG1hcCB0byB0aGUgb3J0aG9jaHJvbWF0aWMgZXJ5dGhyb2JsYXN0IHJlZ2lvbiBhcyB0aGlzIGNlbGwgdHlwZSBoYXMgdmVyeSBsb3cgdHJhbnNjcmlwdGlvbmFsIGRpdmVyc2l0eS4gClRoZXNlIGxvdyBxdWFsaXR5IHF1ZXJ5IGNlbGxzIGRvIG5vdCBoYXZlIGhlbW9nbG9iaW4gZXhwcmVzc2lvbiBhbmQgYXJlIGluIGZhY3QgbWlzLW1hcHBlZDsgdGhleSB3aWxsIGJlIGZsYWdnZWQgYnkgdGhlIFFDIGZpbHRlciBhbmQgZXhjbHVkZWQgZnJvbSBjZWxsIHR5cGUgYXNzaWdubWVudHMuCgoqKlBsZWFzZSBhZGp1c3QgdGhlIE1BRF90aHJlc2hvbGQgKHR5cGljYWxseSBiZXR3ZWVuIDEgYW5kIDMpIGJhc2VkIG9uIHRoZSBkaXN0cmlidXRpb24gb2YgeW91ciBkYXRhc2V0IHRvIGlkZW50aWZ5IHRoZSBvdXRsaWVycyB3aXRoIGxvdyBxdWFsaXR5IGFuZCBoaWdoIG1hcHBpbmcgZXJyb3Igc2NvcmVzLiBUaGlzIHdpbGwgaW1wcm92ZSB5b3VyIGNsYXNzaWZpY2F0aW9ucyBhbmQgYW55IGRvd25zdHJlYW0gY29tcG9zaXRpb24gYW5hbHlzaXMqKgoKCmBgYHtyfQojICMgT3B0aW9uYWwgc3RlcCAtIHJlbW92ZSBvdXRsaWVycyB3aXRoIGhpZ2ggbWFwcGluZyBlcnJvcgojIHF1ZXJ5IDwtIHN1YnNldChxdWVyeSwgbWFwcGluZ19lcnJvcl9RQyA9PSAnUGFzcycpCmBgYAoKT3B0aW9uYWxseSwgb3V0bGllciBjZWxscyB3aXRoIGhpZ2ggbWFwcGluZyBlcnJvciBjYW4gYWxzbyBiZSByZW1vdmVkIGF0IHRoaXMgc3RhZ2UuCkZvciBlYXNlIG9mIGludGVncmF0aW5nIHRoZXNlIG1hcHBlZCBhbm5vdGF0aW9ucyB3aXRoIHRoZSByZXN0IG9mIHlvdXIgYW5hbHlzaXMsIHdlIGNhbiBjaG9vc2UgdG8gc2tpcCB0aGlzIHN0ZXAuIElmIHNvLCBGaW5hbCBDZWxsVHlwZSBhbmQgUHNldWRvdGltZSBwcmVkaWN0aW9ucyB3aWxsIGJlIGFzc2lnbmVkIGFzIE5BIGZvciBjZWxscyBmYWlsaW5nIHRoZSBtYXBwaW5nIGVycm9yIFFDIHRocmVzaG9sZC4gCgoKIyMjIENlbGwgVHlwZSBBc3NpZ25tZW50cwpXZSB3aWxsIG5leHQgdXNlIGEgS05OIGNsYXNzaWZpZXIgdG8gYXNzaWduIGNlbGwgaWRlbnRpdHkgYmFzZWQgb24gdGhlIDMwIEstTmVhcmVzdCBOZWlnaGJvdXJzIGZyb20gdGhlIHJlZmVyZW5jZSBtYXAuClRoaXMgbGFiZWwgdHJhbnNmZXIgc3RlcCB3aWxsIHRha2UgbG9uZ2VyLCBwb3RlbnRpYWxseSBhcm91bmQgMTAgbWludXRlcyBmb3IgfjEwLDAwMCBjZWxscyAKCmBgYHtyLCBmaWcuaGVpZ2h0PTUsIGZpZy53aWR0aD0xMH0KIyBQcmVkaWN0IEhlbWF0b3BvaWV0aWMgQ2VsbCBUeXBlcyBieSBLTk4gY2xhc3NpZmljYXRpb24KcXVlcnkgPC0gcHJlZGljdF9DZWxsVHlwZXMoCiAgcXVlcnlfb2JqID0gcXVlcnksIAogIHJlZl9vYmogPSBCTV9yZWYsIAogIGluaXRpYWxfbGFiZWwgPSAnaW5pdGlhbF9DZWxsVHlwZV9Cb25lTWFycm93TWFwJywgIyBjZWxsdHlwZSBhc3NpZ25tZW50cyBiZWZvcmUgZmlsdGVyaW5nIG9uIG1hcHBpbmcgUUMKICBmaW5hbF9sYWJlbCA9ICdwcmVkaWN0ZWRfQ2VsbFR5cGVfQm9uZU1hcnJvd01hcCcgICMgY2VsbHR5cGUgYXNzaWdubWVudHMgd2l0aCBtYXAgUUMgZmFpbGluZyBjZWxscyBhc3NpZ25lZCBhcyBOQQopIAoKRGltUGxvdChzdWJzZXQocXVlcnksIG1hcHBpbmdfZXJyb3JfUUMgPT0gJ1Bhc3MnKSwgcmVkdWN0aW9uID0gJ3VtYXAnLCBncm91cC5ieSA9IGMoJ3ByZWRpY3RlZF9DZWxsVHlwZV9Cb25lTWFycm93TWFwJyksIAogICAgICAgIHJhc3Rlcj1GQUxTRSwgbGFiZWw9VFJVRSwgbGFiZWwuc2l6ZSA9IDQpCmBgYAoKCiMjIyMgUHNldWRvdGltZSBBbm5vdGF0aW9ucwpXZSBjYW4gYWxzbyBhbm5vdGF0ZSBlYWNoIHF1ZXJ5IGNlbGwgYmFzZWQgb24gdGhlaXIgcG9zaXRpb24gYWxvbmcgaGVtYXRvcG9pZXRpYyBwc2V1ZG90aW1lLiAKUXVlcnkgY2VsbHMgd2lsbCBiZSBhc3NpZ25lZCBhIHBzZXVkb3RpbWUgc2NvcmUgYmFzZWQgb24gdGhlIDMwIEstTmVhcmVzdCBOZWlnaGJvdXJzIGZyb20gdGhlIHJlZmVyZW5jZSBtYXAuClNpbmNlIG91ciBQc2V1ZG90aW1lIEtOTiBhc3NpZ25tZW50cyBhcmUgcGVyZm9ybWVkIGluIFVNQVAgc3BhY2UgKG1vcmUgYWNjdXJhdGUgdGhhbiBLTk4gb24gaGFybW9ueSBjb21wb25lbnRzKSwgdGhpcyBzdGVwIGlzIHZlcnkgZmFzdCAoPCAxMHMpCgpgYGB7cn0KbGlicmFyeShSQ29sb3JCcmV3ZXIpCmBgYAoKCmBgYHtyLCBmaWcuaGVpZ2h0PTQsIGZpZy53aWR0aD0xMn0KIyBQcmVkaWN0IFBzZXVkb3RpbWUgdmFsdWVzIGJ5IEtOTgpxdWVyeSA8LSBwcmVkaWN0X1BzZXVkb3RpbWUoCiAgcXVlcnlfb2JqID0gcXVlcnksIAogIHJlZl9vYmogPSBCTV9yZWYsIAogIGluaXRpYWxfbGFiZWwgPSAnaW5pdGlhbF9Qc2V1ZG90aW1lJywgICMgcHNldWRvdGltZSBhc3NpZ25tZW50cyBiZWZvcmUgZmlsdGVyaW5nIG9uIG1hcHBpbmcgUUMKICBmaW5hbF9sYWJlbCA9ICdwcmVkaWN0ZWRfUHNldWRvdGltZScgICAjIHBzZXVkb3RpbWUgYXNzaWdubWVudHMgd2l0aCBtYXAgUUMgZmFpbGluZyBjZWxscyBhc3NpZ25lZCBhcyBOQQopCgojIFZpc3VhbGl6ZSBIZW1hdG9wb2lldGljIFBzZXVkb3RpbWUgaW4gcXVlcnkgZGF0YQpGZWF0dXJlUGxvdChzdWJzZXQocXVlcnksIG1hcHBpbmdfZXJyb3JfUUMgPT0gJ1Bhc3MnKSwgZmVhdHVyZXMgPSBjKCdwcmVkaWN0ZWRfUHNldWRvdGltZScpLCBzcGxpdC5ieSA9ICdTYW1wbGUnKSAmIAogIHNjYWxlX2NvbG9yX2dyYWRpZW50bihjb2xvcnMgPSByZXYoYnJld2VyLnBhbCgxMSwgJ1JkQnUnKSkpCmBgYAoKIyMjIFZpc3VhbGl6ZSBQcm9qZWN0aW9uIERlbnNpdHkKCk5vdyBsZXQncyB2aXN1YWxpemUgdGhlIGRlbnNpdHkgZGlzdHJpYnV0aW9uIG9mIHF1ZXJ5IGNlbGxzIGFjcm9zcyB0aGUgaGVtYXRvcG9pZXRpYyBoaWVyYXJjaHkKCmBgYHtyLCBmaWcuaGVpZ2h0PTMsIGZpZy53aWR0aD04fQojIFNldCBiYXRjaC9jb25kaXRpb24gdG8gYmUgdmlzdWFsaXplZCBpbmRpdmlkdWFsbHkKYmF0Y2hfa2V5IDwtICdTYW1wbGUnCgojIHJldHVybnMgYSBsaXN0IG9mIHBsb3RzIGZvciBlYWNoIGRvbm9yIGZyb20gYSBwcmUtc3BlY2lmaWVkIGJhdGNoIHZhcmlhYmxlCnByb2plY3Rpb25fcGxvdHMgPC0gcGxvdF9Qcm9qZWN0aW9uX2J5RG9ub3IoCiAgcXVlcnlfb2JqID0gcXVlcnksIAogIGJhdGNoX2tleSA9IGJhdGNoX2tleSwgCiAgcmVmX29iaiA9IEJNX3JlZiwgCiAgSGllcmFyY2h5X29ubHkgPSBGQUxTRSwgIyBXaGV0aGVyIHRvIGV4Y2x1ZGUgVC9OSy9QbGFzbWEvU3Ryb21hbCBjZWxscyAKICBkb3duc2FtcGxlX3JlZmVyZW5jZSA9IFRSVUUsIAogIGRvd25zYW1wbGVfZnJhYyA9IDAuMjUsICAgIyBkb3duLXNhbXBsZSByZWZlcmVuY2UgY2VsbHMgdG8gMjUlOyByZWR1Y2VzIGZpZ3VyZSBmaWxlIHNpemUKICBxdWVyeV9wb2ludF9zaXplID0gMC4yLCAgICMgYWRqdXN0IHNpemUgb2YgcXVlcnkgY2VsbHMgYmFzZWQgb24gIyBvZiBjZWxscwogIHNhdmVwbG90ID0gVFJVRSwgCiAgc2F2ZV9mb2xkZXIgPSAncHJvamVjdGlvbkZpZ3VyZXMvJwopCgojIHNob3cgcGxvdHMgdG9nZXRoZXIgd2l0aCBwYXRjaHdvcmsuIENhbiBhbHNvIGp1c3QgY2FsbCAicHJvamVjdGlvbl9wbG90cyIgb2JqZWN0IHRvIGRpc3BsYXkgb25lLWJ5LW9uZQpwYXRjaHdvcms6OndyYXBfcGxvdHMocHJvamVjdGlvbl9wbG90cywgbmNvbCA9IDIpCmBgYAoKV2UgY2FuIGFsc28gc2V0IEhpZXJhcmNoeV9vbmx5ID0gVFJVRSB0byByZW1vdmUgVC9OSy9QbGFzbWEvU3Ryb21hbCBjZWxscyBhbmQgZm9jdXMgc29sZWx5IG9uIHRoZSBoZW1hdG9wb2lldGljIGhpZXJhcmNoeS4KCmBgYHtyLCBmaWcuaGVpZ2h0PTMsIGZpZy53aWR0aD02LjV9CiMgU2V0IGJhdGNoL2NvbmRpdGlvbiB0byBiZSB2aXN1YWxpemVkIGluZGl2aWR1YWxseQpiYXRjaF9rZXkgPC0gJ1NhbXBsZScKCiMgcmV0dXJucyBhIGxpc3Qgb2YgcGxvdHMgZm9yIGVhY2ggZG9ub3IgZnJvbSBhIHByZS1zcGVjaWZpZWQgYmF0Y2ggdmFyaWFibGUKcHJvamVjdGlvbl9wbG90cyA8LSBwbG90X1Byb2plY3Rpb25fYnlEb25vcigKICBxdWVyeV9vYmogPSBxdWVyeSwgCiAgYmF0Y2hfa2V5ID0gYmF0Y2hfa2V5LCAKICByZWZfb2JqID0gQk1fcmVmLCAKICBIaWVyYXJjaHlfb25seSA9IFRSVUUsICMgV2hldGhlciB0byBleGNsdWRlIFQvTksvUGxhc21hL1N0cm9tYWwgY2VsbHMgCiAgZG93bnNhbXBsZV9yZWZlcmVuY2UgPSBUUlVFLCAKICBkb3duc2FtcGxlX2ZyYWMgPSAwLjI1LCAgICMgZG93bi1zYW1wbGUgcmVmZXJlbmNlIGNlbGxzIHRvIDI1JTsgcmVkdWNlcyBmaWd1cmUgZmlsZSBzaXplCiAgcXVlcnlfcG9pbnRfc2l6ZSA9IDAuMiwgICAjIGFkanVzdCBzaXplIG9mIHF1ZXJ5IGNlbGxzIGJhc2VkIG9uICMgb2YgY2VsbHMKICBzYXZlcGxvdCA9IFRSVUUsIAogIHNhdmVfZm9sZGVyID0gJ3Byb2plY3Rpb25GaWd1cmVzLycKKQoKIyBzaG93IHBsb3RzIHRvZ2V0aGVyIHdpdGggcGF0Y2h3b3JrLiBDYW4gYWxzbyBqdXN0IGNhbGwgInByb2plY3Rpb25fcGxvdHMiIG9iamVjdCB0byBkaXNwbGF5IG9uZS1ieS1vbmUKcGF0Y2h3b3JrOjp3cmFwX3Bsb3RzKHByb2plY3Rpb25fcGxvdHMsIG5jb2wgPSAyKQpgYGAKCgpMZXQncyBzYXZlIHRoZSByZXN1bHRzIG9mIHRoZSBtYXBwaW5nIG9udG8gdGhlIGNvbXBsZXRlIGhlbWF0b3BvaWV0aWMgaGllcmFyY2h5LiAKCmBgYHtyfQpzYXZlUkRTKHF1ZXJ5LCAnUXVlcnlEYXRhX01hcHBlZF9Db21wbGV0ZUhlbWF0b3BvaWVzaXMucmRzJykKYGBgCgoKIyMjIyBCIENFTEwgREVWRUxPUE1FTlQgLSBEb3dubG9hZCByZWZlcmVuY2Ugb2JqZWN0IGFuZCBVTUFQIG1vZGVsCgpTZXQgcHJvamVjdGlvbiBmb2xkZXIsIERvd25sb2FkIHJlZmVyZW5jZSBvYmplY3QgYW5kIFVNQVAgbW9kZWwKCmBgYHtyfQojIFNldCBkaXJlY3RvcnkgdG8gc3RvcmUgcHJvamVjdGlvbiByZWZlcmVuY2UgZmlsZXMKcHJvamVjdGlvbl9wYXRoID0gJy4vJwoKIyBEb3dubG9hZCBCb25lIE1hcnJvdyBSZWZlcmVuY2UgLSAxODcgTWIKY3VybDo6Y3VybF9kb3dubG9hZCgnaHR0cHM6Ly9iZGV2ZWxvcG1lbnRtYXAuczMudXMtZWFzdC0yLmFtYXpvbmF3cy5jb20vQkRldmVsb3BtZW50X1JlZk1hcF9TeW1waG9ueVJlZi5yZHMnLAogICAgICAgICAgICAgICAgICAgIGRlc3RmaWxlID0gcGFzdGUwKHByb2plY3Rpb25fcGF0aCwgJ0JEZXZlbG9wbWVudF9SZWZNYXBfU3ltcGhvbnlSZWYucmRzJykpCiMgRG93bmxvYWQgdXdvdCBtb2RlbCBmaWxlIC0gOTkgTWIKY3VybDo6Y3VybF9kb3dubG9hZCgnaHR0cHM6Ly9iZGV2ZWxvcG1lbnRtYXAuczMudXMtZWFzdC0yLmFtYXpvbmF3cy5jb20vQkRldmVsb3BtZW50X1JlZk1hcF91d290X21vZGVsLnV3b3QnLAogICAgICAgICAgICAgICAgICAgIGRlc3RmaWxlID0gcGFzdGUwKHByb2plY3Rpb25fcGF0aCwgJ0JEZXZlbG9wbWVudF9SZWZNYXBfdXdvdF9tb2RlbC51d290JykpCgojIExvYWQgU3ltcGhvbnkgcmVmZXJlbmNlCmJkZXZfcmVmIDwtIHJlYWRSRFMocGFzdGUwKHByb2plY3Rpb25fcGF0aCwgJ0JEZXZlbG9wbWVudF9SZWZNYXBfU3ltcGhvbnlSZWYucmRzJykpCmJkZXZfcmVmJHVtYXAkZW1iZWRkaW5nWywxXSA9IC1iZGV2X3JlZiR1bWFwJGVtYmVkZGluZ1ssMV0gCgojIFNldCB1d290IHBhdGggZm9yIFVNQVAgcHJvamVjdGlvbgpiZGV2X3JlZiRzYXZlX3V3b3RfcGF0aCA8LSBwYXN0ZTAocHJvamVjdGlvbl9wYXRoLCAnQkRldmVsb3BtZW50X1JlZk1hcF91d290X21vZGVsLnV3b3QnKQpgYGAKCgojIyMjIFZpc3VhbGl6ZSBCb25lIE1hcnJvdyBSZWZlcmVuY2UKCklmIHdlIHdhbnQgdG8gdmlzdWFsaXplIGNlbGx0eXBlIGxhYmVscyBvciBtZXRhZGF0YSBmcm9tIHRoZSBCTSBSZWZlcmVuY2UsIHdlIGNhbiBjcmVhdGUgYSBTZXVyYXQgT2JqZWN0IGZyb20gdGhlIHN5bXBob255IHJlZmVyZW5jZSAKVGhpcyB3aWxsIGJlIG1lbW9yeSBlZmZpY2llbnQgYXMgaXQgd2lsbCBub3QgaW5jbHVkZSBnZW5lIGV4cHJlc3Npb24gY291bnRzLCBvbmx5IHRoZSBVTUFQIGNvb3JkaW5hdGVzIGFuZCB0aGUgbWV0YWRhdGEgaW5jbHVkaW5nIGNlbGwgbGFiZWxzIGFuZCBzb3J0aW5nIGluZm9ybWF0aW9uCgpgYGB7ciwgZmlnLmhlaWdodD01LCBmaWcud2lkdGg9MTF9CmJkZXZfcmVmX29iaiA8LSBjcmVhdGVfUmVmZXJlbmNlT2JqZWN0KGJkZXZfcmVmKQoKRGltUGxvdChiZGV2X3JlZl9vYmosIHJlZHVjdGlvbiA9ICd1bWFwJywgZ3JvdXAuYnkgPSAnQkRldmVsb3BtZW50X0NlbGxUeXBlJywgcmFzdGVyPUZBTFNFLCBsYWJlbD1UUlVFLCBsYWJlbC5zaXplID0gNCkKYGBgCgoKIyMjIE1hcHBpbmcgcXVlcnkgZGF0YSBvbnRvIEIgY2VsbCBkZXZlbG9wbWVudAoKYGBge3J9CnF1ZXJ5IDwtIHJlYWRSRFMoJ1F1ZXJ5RGF0YV9NYXBwZWRfQ29tcGxldGVIZW1hdG9wb2llc2lzLnJkcycpCnF1ZXJ5CmBgYAoKRmlsdGVyIGZvciBjZWxsdHlwZXMgYWxvbmcgQiBjZWxsIGRldmVsb3BtZW50CgpgYGB7cn0KQl9kZXZlbG9wbWVudF9jZWxsdHlwZXMgPC0gYygnSFNDJywgJ0hTQy9NUFAnLCAnTVBQLU15THknLCAnTVBQLUxNUFAnLCAnTE1QUCcsICdFYXJseSBHTVAnLCAnTUxQJywgJ01MUC1JSScsICdQcmUtcERDJywgJ1ByZS1wREMgQ3ljbGluZycsICdwREMnLCAKICAgICAgICAgICAgICAgICAgICAnQ0xQJywgJ0Vhcmx5UHJvQicsICdQcmUtUHJvQicsICdQcm8tQiBWREonLCAnUHJvLUIgQ3ljbGluZycsICdMYXJnZSBQcmUtQicsICdTbWFsbCBQcmUtQicsICdJbW1hdHVyZSBCJywgJ01hdHVyZSBCJykKCnF1ZXJ5IDwtIHN1YnNldChxdWVyeSwgcHJlZGljdGVkX0NlbGxUeXBlX0JvbmVNYXJyb3dNYXAgJWluJSBCX2RldmVsb3BtZW50X2NlbGx0eXBlcykKcXVlcnkKYGBgCgojIyMgTWFwIHRoZSBRdWVyeSBEYXRhClByb3ZpZGUgcmF3IGNvdW50cywgbWV0YWRhdGEsIGFuZCBkb25vciBrZXkuIFRoaXMgc2hvdWxkIHRha2UgPDEgbWluCkNhbGN1bGF0ZSBtYXBwaW5nIGVycm9yIGFuZCBwZXJmb3JtIFFDIHRvIHJlbW92ZSBsb3cgcXVhbGl0eSBjZWxscyB3aXRoIGhpZ2ggbWFwcGluZyBlcnJvcgoKYGBge3J9CiMgYmF0Y2ggdmFyaWFibGUgdG8gY29ycmVjdCBpbiB0aGUgcXVlcnkgZGF0YSwgc2V0IGFzIE5VTEwgaWYgbm8gYmF0Y2hlcyBpbiBxdWVyeQpiYXRjaHZhciA8LSAnU2FtcGxlJwoKIyBNYXAgcXVlcnkgZGF0YXNldCB1c2luZyBTeW1waG9ueSAoS2FuZyBldCBhbCAyMDIxKQpxdWVyeSA8LSBtYXBfUXVlcnkoCiAgICBleHBfcXVlcnkgPSBxdWVyeUBhc3NheXMkUk5BQGNvdW50cywgCiAgICBtZXRhZGF0YV9xdWVyeSA9IHF1ZXJ5QG1ldGEuZGF0YSwKICAgIHJlZl9vYmogPSBiZGV2X3JlZiwKICAgIHZhcnMgPSBiYXRjaHZhcgopCgojIEZsaXAgVU1BUDEgdG8gZ28gZnJvbSBsZWZ0IHRvIHJpZ2h0CnF1ZXJ5W1sndW1hcCddXUBjZWxsLmVtYmVkZGluZ3NbLDFdID0gLXF1ZXJ5W1sndW1hcCddXUBjZWxsLmVtYmVkZGluZ3NbLDFdCmBgYAoKYGBge3J9CnF1ZXJ5W1sndW1hcCddXQpgYGAKCgpgYGB7cn0KYmRldl9yZWYkbWV0YV9kYXRhCmBgYAoKCiMjIyBDZWxsIFR5cGUgQXNzaWdubWVudHMKV2Ugd2lsbCBuZXh0IHVzZSBhIEtOTiBjbGFzc2lmaWVyIHRvIGFzc2lnbiBjZWxsIGlkZW50aXR5IGJhc2VkIG9uIHRoZSAzMCBLLU5lYXJlc3QgTmVpZ2hib3VycyBmcm9tIHRoZSByZWZlcmVuY2UgbWFwLgpUaGlzIGxhYmVsIHRyYW5zZmVyIHN0ZXAgd2lsbCB0YWtlIGxvbmdlciwgcG90ZW50aWFsbHkgYXJvdW5kIDEwIG1pbnV0ZXMgZm9yIH4xMCwwMDAgY2VsbHMgCgpgYGB7ciwgZmlnLmhlaWdodD01LCBmaWcud2lkdGg9MTB9CiMgUHJlZGljdCBIZW1hdG9wb2lldGljIENlbGwgVHlwZXMgYnkgS05OIGNsYXNzaWZpY2F0aW9uCnF1ZXJ5IDwtIHByZWRpY3RfQ2VsbFR5cGVzKAogIHF1ZXJ5X29iaiA9IHF1ZXJ5LCAKICByZWZfb2JqID0gYmRldl9yZWYsIAogIHJlZl9sYWJlbCA9ICdCRGV2ZWxvcG1lbnRfQ2VsbFR5cGUnLCAgICMjIGZvciBhIG1vcmUgZGV0YWlsZWQgYW5ub3RhdGlvbiwgdXNlIEJEZXZlbG9wbWVudF9DZWxsVHlwZV9Db21wcmVoZW5zaXZlCiAgaW5pdGlhbF9sYWJlbCA9ICdpbml0aWFsX0NlbGxUeXBlX0JEZXZlbG9wbWVudCcsICMgY2VsbHR5cGUgYXNzaWdubWVudHMgYmVmb3JlIGZpbHRlcmluZyBvbiBtYXBwaW5nIFFDCiAgZmluYWxfbGFiZWwgPSAncHJlZGljdGVkX0NlbGxUeXBlX0JEZXZlbG9wbWVudCcgICMgY2VsbHR5cGUgYXNzaWdubWVudHMgd2l0aCBtYXAgUUMgZmFpbGluZyBjZWxscyBhc3NpZ25lZCBhcyBOQQopIAoKRGltUGxvdChzdWJzZXQocXVlcnksIG1hcHBpbmdfZXJyb3JfUUMgPT0gJ1Bhc3MnKSwgcmVkdWN0aW9uID0gJ3VtYXAnLCBncm91cC5ieSA9IGMoJ3ByZWRpY3RlZF9DZWxsVHlwZV9CRGV2ZWxvcG1lbnQnKSwgCiAgICAgICAgcmFzdGVyPUZBTFNFLCBsYWJlbD1UUlVFLCBsYWJlbC5zaXplID0gNCkKYGBgCgoKIyMjIFZpc3VhbGl6ZSBQcm9qZWN0aW9uIERlbnNpdHkKCk5vdyBsZXQncyB2aXN1YWxpemUgdGhlIGRlbnNpdHkgZGlzdHJpYnV0aW9uIG9mIHF1ZXJ5IGNlbGxzIGFjcm9zcyB0aGUgaGVtYXRvcG9pZXRpYyBoaWVyYXJjaHkKCmBgYHtyLCBmaWcuaGVpZ2h0PTMsIGZpZy53aWR0aD04fQojIFNldCBiYXRjaC9jb25kaXRpb24gdG8gYmUgdmlzdWFsaXplZCBpbmRpdmlkdWFsbHkKYmF0Y2hfa2V5IDwtICdTYW1wbGUnCgojIHJldHVybnMgYSBsaXN0IG9mIHBsb3RzIGZvciBlYWNoIGRvbm9yIGZyb20gYSBwcmUtc3BlY2lmaWVkIGJhdGNoIHZhcmlhYmxlCnByb2plY3Rpb25fcGxvdHNfYmRldiA8LSBwbG90X1Byb2plY3Rpb25fYnlEb25vcigKICBxdWVyeV9vYmogPSBxdWVyeSwgCiAgYmF0Y2hfa2V5ID0gYmF0Y2hfa2V5LCAKICByZWZfb2JqID0gYmRldl9yZWYsIAogIGRvd25zYW1wbGVfcmVmZXJlbmNlID0gVFJVRSwgCiAgZG93bnNhbXBsZV9mcmFjID0gMC4yNSwgICAjIGRvd24tc2FtcGxlIHJlZmVyZW5jZSBjZWxscyB0byAyNSU7IHJlZHVjZXMgZmlndXJlIGZpbGUgc2l6ZQogIHF1ZXJ5X3BvaW50X3NpemUgPSAwLjIsICAgIyBhZGp1c3Qgc2l6ZSBvZiBxdWVyeSBjZWxscyBiYXNlZCBvbiAjIG9mIGNlbGxzCiAgc2F2ZXBsb3QgPSBUUlVFLCAKICBzYXZlX2ZvbGRlciA9ICdwcm9qZWN0aW9uRmlndXJlcy8nCikKCiMgc2hvdyBwbG90cyB0b2dldGhlciB3aXRoIHBhdGNod29yay4gQ2FuIGFsc28ganVzdCBjYWxsICJwcm9qZWN0aW9uX3Bsb3RzIiBvYmplY3QgdG8gZGlzcGxheSBvbmUtYnktb25lCnBhdGNod29yazo6d3JhcF9wbG90cyhwcm9qZWN0aW9uX3Bsb3RzX2JkZXYsIG5jb2wgPSAyKQpgYGAKCgpgYGB7cn0Kc2F2ZVJEUyhxdWVyeSwgJ1F1ZXJ5RGF0YV9NYXBwZWRfQmNlbGxEZXZlbG9wbWVudC5yZHMnKQpgYGAKCgoKCgoK